local oo = require("SharedLibs.BaseLib")

local coroHooker = require("SharedLibs.CoroHooker");
coroHooker.HookCoroutine();

AsyncOpState = require("SharedLibs.AsyncOpState");
Debugger = require("SharedLibs.Debugger");

--- AsyncOp Class ---
local AsyncOp = oo.Class()
AsyncOp.name = "AsyncOp"

function AsyncOp.prototype.constructor(self)
	self._name = nil
	self._state = AsyncOpState.Ready
	self._parent = nil
	--- 注意：由于lua不支持方法委托（实际上lua没有成员方法），所以_continuation在被调用时
	--- 第一个参数为Start()方法传入的parent，也就是执行_continuation(parent, self)
	self._continuation = nil
	self._onError = nil
end

oo.SetDescriptor(
	AsyncOp.prototype,
	"isAlive",
	{
		get = function(self)
			return self._state == AsyncOpState.Executing 
				or self._state == AsyncOpState.Paused;
		end
	},
	true
)
oo.SetDescriptor(
	AsyncOp.prototype,
	"isPaused",
	{
		get = function(self)
			return self._state == AsyncOpState.Paused;
		end
	},
	true
)
oo.SetDescriptor(
	AsyncOp.prototype,
	"isCombinedOp",
	{
		get = function(self)
			return false;
		end
	},
	true
)
oo.SetDescriptor(
	AsyncOp.prototype,
	"name",
	{
		get = function(self)
			return self._name
		end,
		set = function(self, name)
			self._name = name
		end
	},
	true
)
oo.SetDescriptor(
	AsyncOp.prototype,
	"state",
	{
		get = function(self)
			return self._state
		end
	},
	true
)
oo.SetDescriptor(
	AsyncOp.prototype,
	"classID",
	{
		get = function(self)
			return "IAsyncOp"
		end
	},
	true
)
oo.SetDescriptor(
	AsyncOp.prototype,
	"parent",
	{
		get = function(self)
			return self._parent
		end
	},
	true
)

--- 调整一个运行中的Op的continuation.
--- 用于某些底层库(例如EffectMgr), 上层尽量不要调用!
function AsyncOp.prototype:SetContinuation(continuation)
	assert(self.isAlive);
	self._continuation = continuation;
end

--- 用来设置参数
--- 例如, coroutine可以通过SetParams来设置co的入口参数
function AsyncOp.prototype:SetParams(...)
end

--- [Block]
--- 执行这个Op, 并等待Op执行完毕. 注: 只能在Coroutine内部调用!
function AsyncOp.prototype:Wait()
	return coroHooker.Yield(self);
end

--- useLoop 指对于Start方法里立刻Stop掉的Op，是递归调用还是循环调用。
--- 递归调用指直接使用continuation调用，过多的嵌套会导致堆栈溢出。
--- 循环调用不会溢出，但需要调用者在Start()后对IsDone()进行判断，并执行循环逻辑。
--- 默认为递归调用（简单些）
function AsyncOp.prototype:Start(continuation, parent, useLoop)
	assert(self._state == AsyncOpState.Ready);
	self._state = AsyncOpState.Executing
	self._parent = parent

	if useLoop then
		self:_onStart();

		if self._state ~= AsyncOpState.Stopped then
			if continuation ~= nil then 
				self._continuation = continuation
			end;
		end
	else
		self._continuation = continuation;
		self:_onStart();
	end	
end

function AsyncOp.prototype:Reset(...)
	if self._state == AsyncOpState.Ready then return end

	if self._state == AsyncOpState.Executing 
		or self._state == AsyncOpState.Paused then
		self:Stop();
	end
	self:_onReset(...);

	self._state = AsyncOpState.Ready;
	self._parent = nil;
	self._continuation = nil;
end

--- 注：Restart不传递参数给Start(). 所以如果要传递参数，请先Reset(), 然后Start(...)
function AsyncOp.prototype:Restart()
	self:Reset(); 
	self:Start();
end

--- Override this in subclass if needed!
function AsyncOp.prototype:_onStart() end
function AsyncOp.prototype:_onStop(isComplete) end
function AsyncOp.prototype:_onPause() end
function AsyncOp.prototype:_onResume() end
function AsyncOp.prototype:_onReset(...) end

--- When operation is complete normally, please call _doStop(true)
--- Override _onStop() if your operation need additional logic.
function AsyncOp.prototype:_doStop(isComplete)
	--- 如果你在LambdaOp里直接coroutine:Stop(), 就出现这种情况
	--- 实际上, 如果你在Coroutione代码里直接coroutine:Stop(), 它
	--- 就会执行一次 LambdaOp, 并在lambdaOp里再执行相关stop代码
	if AsyncOpState.Stopped == self._state then return end;

	Debugger.Assert(AsyncOpState.Ready ~= self._state and AsyncOpState.Released ~= self._state);

	self._state = AsyncOpState.Stopped;
	local parent = self._parent;
	self._parent = nil;

	local continuation = self._continuation;
	self._continuation = nil;

	self:_onStop(isComplete);

	--if (nil == _parent.p) CoroMgr._Instance.RemoveTopOp(this);
	if self._state == AsyncOpState.Stopped then
		if isComplete and continuation then
			continuation(parent, self);
		end
	end
end

function AsyncOp.prototype:Stop()
	if AsyncOpState.Ready == self._state 
		or AsyncOpState.Stopped == self._state then
		return;
	end

	self:_doStop(false);
end

function AsyncOp.prototype:Pause()
	if (AsyncOpState.Ready == self._state) or (AsyncOpState.Stopped == self._state) then
		return
	end
	self._state = AsyncOpState.Paused
	self:_onPause();
end

function AsyncOp.prototype:Resume()
	if (AsyncOpState.Ready == self._state) or (AsyncOpState.Stopped == self._state) then
		return
	end
	self._state = AsyncOpState.Executing
	self:_onResume();
end

function AsyncOp.prototype:IsDone()
	return self._state == AsyncOpState.Stopped
end

function AsyncOp.prototype:GetReturn()
	return nil
end

function AsyncOp.prototype:GetDesc()
	if self._name == nil then
		return self._class.name;
	else
		return self._name;
	end
end

function AsyncOp.OnUnhandledError(self, onUnhandledException)
	AsyncOp._onUnhandledError = onUnhandledException
end

function AsyncOp._HandleException(op, e)
	if AsyncOp._onUnhandledError then
		AsyncOp._onUnhandledError(op, e)	
	else
		if Debugger.IsStudio then
			warn(e.stack);
			warn(e.message);
		end		
		error(e.stack.."\n"..e.message)
	end
end

function AsyncOp.prototype:OnError(onError)
	assert(not self.isAlive)
	self._onError = onError
	return self
end

function AsyncOp.prototype.HandleAsyncException(self, errSrcOp, err, isSelfErr)
	local handled = false
	if self._onError ~= nil then
		handled = self._onError(err)
		if handled then
			if isSelfErr then
				if self._continuation ~= nil then
					self._continuation(self._parent, self)
				end
			end
		else
			self:Stop()
		end
	else
		if self._parent ~= nil then
			handled = self._parent:HandleAsyncException(errSrcOp, err, false)
			if handled then
				if isSelfErr then
					if self._continuation ~= nil then
						self._continuation(self._parent, self)
					end
				end
			else
				self:Stop()
			end
		else
			AsyncOp._HandleException(errSrcOp, err);
			self:Stop()
		end
	end
	return handled
end
function AsyncOp.prototype.HandleCodeException(self, err)
	if self._onError ~= nil then
		self._onError(err)
	else
		if self._parent ~= nil then
			self._parent:HandleCodeException(err)
		else
			AsyncOp._HandleException(self, err)
		end
	end
	self:Stop()
end

return AsyncOp;
